/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package rfid;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.TimeoutException;
import org.llrp.ltk.exceptions.InvalidLLRPMessageException;
import org.llrp.ltk.generated.enumerations.*;
import org.llrp.ltk.generated.interfaces.*;
import org.llrp.ltk.generated.messages.*;
import org.llrp.ltk.generated.parameters.*;
import org.llrp.ltk.generated.parameters.RFTransmitter;
import org.llrp.ltk.net.*;
import org.llrp.ltk.types.*;

public class LTKInventory implements LLRPEndpoint {

    private LLRPConnection reader;
    private static final int TIMEOUT_MS = 30000;
    private static final int ROSPEC_ID = 123;
    private GestorTags gestorTags;
    private boolean connected = false;
    private Properties conf;

    LTKInventory(Properties configuracion) {
        conf = configuracion;
    }

    // Build the ROSpec.
    // An ROSpec specifies start and stop triggers,
    // tag report fields, antennas, etc.
    public ROSpec buildROSpec(int rospec) {
        System.out.println("Building the ROSpec.");

        // Create a Reader Operation Spec (ROSpec).
        ROSpec roSpec = new ROSpec();

        roSpec.setPriority(new UnsignedByte(0));
        roSpec.setCurrentState(new ROSpecState(ROSpecState.Disabled));
        roSpec.setROSpecID(new UnsignedInteger(rospec));

        // Set up the ROBoundarySpec
        // This defines the start and stop triggers.
        ROBoundarySpec roBoundarySpec = new ROBoundarySpec();

        // Set the start trigger to null.
        // This means the ROSpec will start as soon as it is enabled.
        ROSpecStartTrigger startTrig = new ROSpecStartTrigger();
        startTrig.setROSpecStartTriggerType(new ROSpecStartTriggerType(ROSpecStartTriggerType.Null));
        roBoundarySpec.setROSpecStartTrigger(startTrig);

        // Set the stop trigger is null. This means the ROSpec
        // will keep running until an STOP_ROSPEC message is sent.
        ROSpecStopTrigger stopTrig = new ROSpecStopTrigger();
        stopTrig.setDurationTriggerValue(new UnsignedInteger(0));
        stopTrig.setROSpecStopTriggerType(new ROSpecStopTriggerType(ROSpecStopTriggerType.Null));
        roBoundarySpec.setROSpecStopTrigger(stopTrig);

        roSpec.setROBoundarySpec(roBoundarySpec);

        // Add an Antenna Inventory Spec (AISpec).
        AISpec aispec = new AISpec();

        // Set the AI stop trigger to null. This means that
        // the AI spec will run until the ROSpec stops.
        AISpecStopTrigger aiStopTrigger = new AISpecStopTrigger();
        aiStopTrigger.setAISpecStopTriggerType(new AISpecStopTriggerType(AISpecStopTriggerType.Null));
        aiStopTrigger.setDurationTrigger(new UnsignedInteger(0));
        aispec.setAISpecStopTrigger(aiStopTrigger);

        // Select which antenna ports we want to use.
        // Setting this property to zero means all antenna ports.
        String antenas = conf.getProperty("antenasActivas");
        UnsignedShortArray antennaIDs = new UnsignedShortArray();
        String[] activas = antenas.split(",");
        
        for (int i = 0; i < activas.length; i++) {
            UnsignedShort id = new UnsignedShort(activas[i]);
            antennaIDs.add(id);           
        }

        aispec.setAntennaIDs(antennaIDs);

        // Tell the reader that we're reading Gen2 tags.
        InventoryParameterSpec inventoryParam = new InventoryParameterSpec();
        inventoryParam.setProtocolID(new AirProtocols(AirProtocols.EPCGlobalClass1Gen2));
        inventoryParam.setInventoryParameterSpecID(new UnsignedShort(1));

        /****************** CUSTOM ******************************************/
        // Create a custom parameter (Inventory Search Mode)
        Custom searchMode = new Custom();
        BytesToEnd_HEX data = new BytesToEnd_HEX();
// Set the data to 0x0001 (Single target)
// Dual target = 0x0002
        data.add(new SignedByte(0));
        data.add(new SignedByte(2));
        System.out.println("BytesHEX = " + data.toString());

// 25882 is the vendor ID for Impinj
        searchMode.setVendorIdentifier(new UnsignedInteger(25882));
// Parameter 23 = ImpinjInventorySearchType
        searchMode.setParameterSubtype(new UnsignedInteger(23));
        searchMode.setData(data);

// Set the session to Session 2
// Session is a standard LLRP setting
        C1G2InventoryCommand invCmd = new C1G2InventoryCommand();
        invCmd.setTagInventoryStateAware(new Bit(0));
        C1G2SingulationControl c1g2SingCtrl = new C1G2SingulationControl();
        TwoBitField session = new TwoBitField();
        session.clear(0);
        //session.clear(1);
        //session.set(0);
        session.set(1);
        c1g2SingCtrl.setSession(session);
// Tag population
        c1g2SingCtrl.setTagPopulation(new UnsignedShort(32));
// Tag transit time
        c1g2SingCtrl.setTagTransitTime(new UnsignedInteger(0));
        invCmd.setC1G2SingulationControl(c1g2SingCtrl);
// Add our custom setting to the C1G2InventoryCommand
        invCmd.addToCustomList(searchMode);
        /************** FIN CUSTOM ********************************************/


        for (int i = 0; i < activas.length; i++) {
            UnsignedShort id = new UnsignedShort(activas[i]);
            AntennaConfiguration antennaConfig = new AntennaConfiguration();
            antennaConfig.setAntennaID(id);
            antennaConfig.addToAirProtocolInventoryCommandSettingsList(invCmd);
            inventoryParam.addToAntennaConfigurationList(antennaConfig);
        }


        aispec.addToInventoryParameterSpecList(inventoryParam);




        roSpec.addToSpecParameterList(aispec);

        // Specify what type of tag reports we want
        // to receive and when we want to receive them.
        ROReportSpec roReportSpec = new ROReportSpec();
        // Receive a report every time a tag is read.
        roReportSpec.setROReportTrigger(new ROReportTriggerType(ROReportTriggerType.Upon_N_Tags_Or_End_Of_ROSpec));
        roReportSpec.setN(new UnsignedShort(1));
        TagReportContentSelector reportContent =
                new TagReportContentSelector();
        // Select which fields we want in the report.
        reportContent.setEnableAccessSpecID(new Bit(1));
        reportContent.setEnableAntennaID(new Bit(1));
        reportContent.setEnableChannelIndex(new Bit(1));
        reportContent.setEnableFirstSeenTimestamp(new Bit(1));
        reportContent.setEnableInventoryParameterSpecID(new Bit(1));
        reportContent.setEnableLastSeenTimestamp(new Bit(1));
        reportContent.setEnablePeakRSSI(new Bit(1));
        reportContent.setEnableROSpecID(new Bit(1));
        reportContent.setEnableSpecIndex(new Bit(1));
        reportContent.setEnableTagSeenCount(new Bit(1));
        roReportSpec.setTagReportContentSelector(reportContent);
        roSpec.setROReportSpec(roReportSpec);


        return roSpec;
    }

    // Add the ROSpec to the reader.
    public void addROSpec(ROSpec roSpec) {
        ADD_ROSPEC_RESPONSE response;


        System.out.println("Adding the ROSpec.");
        try {
            ADD_ROSPEC roSpecMsg = new ADD_ROSPEC();
            roSpecMsg.setROSpec(roSpec);
            response = (ADD_ROSPEC_RESPONSE) reader.transact(roSpecMsg, TIMEOUT_MS);
            System.out.println(response.toXMLString());

            // Check if the we successfully added the ROSpec.
            StatusCode status = response.getLLRPStatus().getStatusCode();
            if (status.equals(new StatusCode("M_Success"))) {
                System.out.println("Successfully added ROSpec.");
            } else {
                System.out.println("Error adding ROSpec.");
                System.exit(1);
            }
        } catch (InvalidLLRPMessageException ex) {
            System.out.println("InvalidLLRPMessageException adding ROSpec.");
            ex.printStackTrace();
        } catch (TimeoutException e) {
            System.out.println("TimeoutException adding ROSpec.");
            e.printStackTrace();
        }
    }

    // Enable the ROSpec.
    public void enableROSpec(int roSpec) {
        ENABLE_ROSPEC_RESPONSE response;

        System.out.println("Enabling the ROSpec.");
        ENABLE_ROSPEC enable = new ENABLE_ROSPEC();
        enable.setROSpecID(new UnsignedInteger(roSpec));
        try {
            response = (ENABLE_ROSPEC_RESPONSE) reader.transact(enable, TIMEOUT_MS);
            System.out.println(response.toXMLString());
        } catch (Exception e) {
            System.out.println("Error enabling ROSpec.");
            e.printStackTrace();
        }
    }

    // Start the ROSpec.
    public void startROSpec(int roSpec) {
        START_ROSPEC_RESPONSE response;
        System.out.println("Starting the ROSpec.");
        START_ROSPEC start = new START_ROSPEC();
        start.setROSpecID(new UnsignedInteger(roSpec));
        try {
            response = (START_ROSPEC_RESPONSE) reader.transact(start, TIMEOUT_MS);
            System.out.println(response.toXMLString());

        } catch (Exception e) {
            System.out.println("Error deleting ROSpec.");
            e.printStackTrace();
        }
    }

    // Delete all ROSpecs from the reader.
    public void deleteROSpecs() {
        DELETE_ROSPEC_RESPONSE response;

        System.out.println("Deleting all ROSpecs.");
        DELETE_ROSPEC del = new DELETE_ROSPEC();
        // Use zero as the ROSpec ID.
        // This means delete all ROSpecs.
        del.setROSpecID(new UnsignedInteger(0));
        try {
            response = (DELETE_ROSPEC_RESPONSE) reader.transact(del, TIMEOUT_MS);
            System.out.println(response.toXMLString());
        } catch (Exception e) {
            System.out.println("Error deleting ROSpec.");
            e.printStackTrace();
        }
    }

    // This function gets called asynchronously
    // when a tag report is available.
    public void messageReceived(LLRPMessage message) {
        if (message.getTypeNum() == RO_ACCESS_REPORT.TYPENUM) {
            // The message received is an Access Report.
            RO_ACCESS_REPORT report = (RO_ACCESS_REPORT) message;
            // Get a list of the tags read.
            List<TagReportData> tags =
                    report.getTagReportDataList();
            // Loop through the list and get the EPC of each tag.
            for (TagReportData tag : tags) {

                //System.out.println(tag.getEPCParameter()); //identificador del tag
                //System.out.println(tag.getLastSeenTimestampUTC()); //Ultima vez que se leyo
                //System.out.println(tag.getAntennaID()); //identificador de la antena
                //System.out.println(tag.getChannelIndex()); //indice del canal
                //System.out.println(tag.getFirstSeenTimestampUTC()); //Primera vez que se leyo
                //System.out.println(tag.getTagSeenCount()); //Numero de veces leido
                //System.out.println(tag.getPeakRSSI()); //no lo se
                //System.out.println(tag.getTypeNum());

                TagData t = gestorTags.get(tag.getEPCParameter().toString());
                if (t == null) {
                    gestorTags.add(tag.getEPCParameter().toString(), new TagData(tag));
                    //System.out.println("NUEVO TAG. Antenna-> " + tag.getAntennaID());
                } else {
                    gestorTags.update(t.getEPCParameter().toString(), new TagData(tag));
                    //System.out.println("Actualizacion. Antenna-> " + tag.getAntennaID());
                }
            }
        }
    }

    // This function gets called asynchronously
    // when an error occurs.
    public void errorOccured(String s) {
        System.out.println("An error occurred: " + s);
    }

    // Connect to the reader
    public void connect(String hostname) {
        // Create the reader object.
        if (connected) {
            return;
        }

        reader = new LLRPConnector(this, hostname);

        // Try connecting to the reader.
        try {
            System.out.println("Connecting to the reader.");
            ((LLRPConnector) reader).connect();
            connected = true;
        } catch (LLRPConnectionAttemptFailedException e1) {
            e1.printStackTrace();
            System.exit(1);
        }
    }

    // Disconnect from the reader
    public void disconnect() {
        ((LLRPConnector) reader).disconnect();
        connected = false;
    }

    // Connect to the reader, setup the ROSpec
    // and run it.
    public void run(String hostname) {
        connect(hostname);
        enableImpinjExt();
        deleteROSpecs();
        addROSpec(buildROSpec(ROSPEC_ID));
        enableROSpec(ROSPEC_ID);
        startROSpec(ROSPEC_ID);
    }

    // Cleanup. Delete all ROSpecs
    // and disconnect from the reader.
    public void stop() {
        deleteROSpecs();
        disconnect();
    }

    void setGestorTags(GestorTags gestorTags) {
        this.gestorTags = gestorTags;
    }

    GestorTags getGestorTags() {
        return gestorTags;
    }

    public LLRPConnection getConnection(String hostname) {
        connect(hostname);
        return reader;
    }

    private AntennaConfiguration crearAntennaConfiguration(UnsignedShort antennaId, int rcvSesitivity, int txPower) {
        AntennaConfiguration ac = new AntennaConfiguration();
        ac.setAntennaID(antennaId);


        System.out.println("->" + ac.getAntennaID());
        System.out.println("->" + ac.getAirProtocolInventoryCommandSettingsList());
        System.out.println("->" + ac.toString());
        RFReceiver rc = new RFReceiver();




        RFTransmitter rt = new RFTransmitter();
        rt.setTransmitPower(new UnsignedShort(91));
        ac.setRFTransmitter(rt);
        ac.setRFReceiver(rc);
        return ac;
    }

    public void enableImpinjExt() {
        BytesToEnd_HEX data = new BytesToEnd_HEX();
        CUSTOM_MESSAGE msg = new CUSTOM_MESSAGE();
        msg.setVendorIdentifier(new UnsignedInteger(25882));
        msg.setMessageSubtype(new UnsignedByte(21));
        data.add(new SignedByte(0));
        data.add(new SignedByte(0));
        data.add(new SignedByte(0));
        data.add(new SignedByte(0));
        msg.setData(data);
        reader.send(msg);
    }
}
